package socket;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

/**
 * @author DBX
 * @effect 聊天室服务器
 * @date 2021/8/30 0030 - 15:56
 */
public class Server {
    private ServerSocket serverSocket;
//    共享所有ClientHandler中的输出流便于广播消息进行输出
//    private PrintWriter[] allOut = {};
    private Collection<PrintWriter> allOut = new ArrayList<>();
    Socket socket = null;
    public Server(){
        try {
           /* 运行在服务端的ServerSocket主要有两个工作
                    1.打开服务端端口，客户端就可以通过这个端口对服务器进行连接
                    2.监听服务端口，一旦一个客户端与服务端建立连接，就会返回一个Socket对象，通过它进行交互*/
            System.out.println("服务端正在启动...");
//            该端口不能与系统其他应用程序已经打开的端口号一致，否在抛出java.net.BindException:address already in use
            serverSocket = new ServerSocket(8101);
            System.out.println("服务端启动成功");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public void start(){
        try {
            /*
            * ServerSocket提供的方法
            * Socket accept()
            * 此方法是一个阻塞方法，调用后服务器程序“卡住”，开始等待客户端连接，一旦一个客户端连接了此时方法会立即返回Socket
            * 通过返回的Socket即可与连接的客户端进行交互*/

            while (true) {
                System.out.println("等待客户端连接");
                socket = serverSocket.accept();
                System.out.println(socket.getInetAddress().getHostAddress()+"一个客户端连接了");
                Thread t = new Thread(new ClientHandler(socket));
                t.start();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }

//    该线程任务时负责与指定的客户端交互的
    class ClientHandler implements Runnable{

        private Socket socket;
        private String host;

        public ClientHandler(Socket socket){
            this.socket = socket;
//            通过socket获取当前地址
            host = socket.getInetAddress().getHostAddress();
        }
        @Override
        public void run() {
            BufferedReader br = null;
            String message ;
            PrintWriter pw = null;
            try {
                br = new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));
                pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(),"UTF-8")),true);
//                将输出流存入allOut便于其他ClientHandler访问来转发消息
//                        1.对allOut数组扩容
              synchronized (Server.this) {
//                    allOut = Arrays.copyOf(allOut,allOut.length+1);
////                2.将当前输出流存入数组最后一个位置
//                    allOut[allOut.length-1]  = pw;
                allOut.add(pw);
              }
                sendMessage(host+"上线了，当前在线人数"+ allOut.size());

                while (true){
                    if (!((message = br.readLine())!=null)) break;
                    System.out.println(host+"说："+message);
//                    将消息回复给客户端
                   sendMessage(host+"说:"+message);
                }
            } catch (IOException e) {

            }finally {
//                处理客户端断开后的操作
                synchronized (Server.this) {
//                    for (int i = 0; i < allOut.length; i++) {
//                        if (allOut[i] == pw){
//                            allOut[i] = allOut[allOut.length-1];
//                            allOut = Arrays.copyOf(allOut,allOut.length-1);
//                            break;
//                        }
//                    }
                    allOut.remove(pw);
                }
                sendMessage(host+"下线了，当前在线人数："+allOut.size());
                if (socket != null) {
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (br != null) {
                    try {
                        br.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }

            /*
             * 这里的br.readLine()在读取客户端发送的数据时，会形成阻塞，如果没有客户端发送消息时，会一直卡住
             * 如果客户端调用了socket.close()与服务端断开连接，那么readLine方法会返回null
             * 如果客户端强行关闭（没有调用socket.close()就结束了程序）则readLine方法会抛出异常
             *       java.net.SocketException:Connection reset*/

        }
//        将消息广播给所有客户端
        private void sendMessage(String message){

            synchronized (Server.this) {
//                for (int i = 0;i<allOut.length;i++) {
//                    allOut[i].println(message);
//                }
                for (PrintWriter printWriter : allOut) {
                    printWriter.println(message);
                }
            }
        }
    }
}
